//
// netd.c : wrapper for ziproxy - user mode inetd replacement
// 2002 Juraj Variny - variny@naex.sk
// 
// Based on
// proxy.c from microproxy package:
// (c) May 2001, by David McNab - david@rebirthing.co.nz, http://freeweb.sf.org
// 
// Released subject to GNU General Public License.
//
#define LISTEN_PORT 8080

#define TIMEOUT 240 //seconds

// Full path/name of executable
#define EXECUTE_NAME "./ziproxy" 

#ifdef LOGGING

//Used with strftime() function
#define LOGNAMEFMT "%j-%Y_%T.log.bz2" 

//Compression program to use for logging
static char *command[] = {"bzip2", "-3cq", "-", (char*)0}; 
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <time.h>

typedef int SOCKET;
typedef unsigned int UINT;

/*#define SockSend(sd, buf, len)			write(sd, buf, len)
#define SockReceive(sd, buf, len)		read(sd, buf, len)
#define SockClose(sd)						close(sd)*/
#define SockAddrType					struct sockaddr_in
#define SockError(msg)					  fprintf(stderr,"netd:%s - %s\n",msg,strerror(errno))

//#define Strnicmp(s1, s2, n)					strncasecmp(s1, s2, n)


//
// PRIVATE DECLARATIONS
//

int		proxy_server();
int		proxy_handlereq(SOCKET socket);

void 	sigcatch(int);

int main(int argc, char*argv[], char *env[])
{
	struct in_addr addr;
	struct hostent * addr2 = NULL;
#ifdef LOGGING
#ifdef LOG_PIPE
	struct tm *btime;
	time_t cas;
	char logfilename[32];
	int pipedes[2],logdes;
#endif
#endif
	
	if(argc > 2){
	    fprintf(stderr,"Too much arguments\n");
	    return 3;
	}else if(argc == 2) {
		if(!inet_aton(argv[1],&addr) && !(addr2 = gethostbyname(argv[1]))){
			fprintf(stderr,"netd:Invalid address or host name '%s'\n",argv[1]);
			return 3;
		}
		if(addr2) addr=*((struct in_addr *)addr2->h_addr);
	}else addr.s_addr=0;
	
#ifdef TIMEOUT
	signal(SIGALRM, sigcatch);
#endif
#ifdef LOGGING
#ifdef LOG_PIPE
	time(&cas);
	btime = localtime(&cas);
	strftime(logfilename,sizeof(logfilename),LOGNAMEFMT,btime);
	if((logdes = open(logfilename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)) == -1)
	{
			fprintf(stderr, "Error while creating logfile\n");
			return 4;
	}
	fflush(stderr);
	pipe(pipedes);


	switch(fork())
	{
					
			case 0: //child
					//should have a chance to write out last line(s) of logging output:
					signal(SIGPIPE,SIG_IGN);
					signal(SIGHUP,SIG_IGN);
					signal(SIGINT,SIG_IGN);
					signal(SIGQUIT,SIG_IGN);

					//stdin <- pipe
					 dup2(pipedes[0],0);
					 close(pipedes[0]);
					 close(pipedes[1]);
					 
					//stdout -> logfile
					 dup2(logdes,1);
					 close(logdes);
					 execvp(command[0],command);
					 fprintf(stderr, "Error running %s\n",command[0]);
					return 6;

			case -1:fprintf(stderr, "Error while fork()\n");
					return 5;

			default://parent
					
					signal(SIGHUP, sigcatch);
					signal(SIGINT, sigcatch);
					signal(SIGTERM, sigcatch);

					//stderr->pipe
					dup2(pipedes[1],2);
					close(pipedes[1]);
					close(pipedes[0]);
					close(logdes);
					break;
	}
#elif defined LOG_FILE
	if(!freopen(LOG_FILE,"a",stderr)) 
	{
			fprintf(stderr, "Error while creating logfile\n");
			return 4;
	}
#else
#error If you turn on LOGGING, choose betwen LOG_PIPE and LOG_FILE.
#endif //LOG_PIPE
	
setvbuf(stderr,NULL,_IOLBF,BUFSIZ);
#endif //LOGGING
	return proxy_server(&addr);
}


//
// main thread which listens for incoming http connections, and launches a child process
// when a connection comes in
//
int proxy_server(struct in_addr *addr)
{
	SOCKET			  sock_listen, sock_client;
	int						one = 1,sin_size,Status;

	SockAddrType	sockAddr,gotConn;
	
	// loop around waiting for client connection or change of listen port
	while (1)
	{
		// create the socket
	sockAddr.sin_family=AF_INET;
	sockAddr.sin_port=htons(LISTEN_PORT);
	sockAddr.sin_addr.s_addr=INADDR_ANY;

	// bind the socket
	sock_listen = socket(AF_INET, SOCK_STREAM, 0);
	//Status = setsockopt (sock_listen, IPPROTO_TCP, TCP_NODELAY, (char * ) &one, sizeof (int));
	Status = bind(sock_listen, (struct sockaddr*) &sockAddr, sizeof(sockAddr));
	if(Status < 0)
	{
		SockError("Failed to connect socket for receiving connections");
		return 1;
	}
		// set socket to listen
	if (listen(sock_listen, SOMAXCONN) != 0)
	{
		SockError("Failed to listening mode on socket");
		return 1;
	}
	if(setsockopt(sock_listen,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(int)) < 0)
		SockError ("Failed to set REUSEADDR flag on socket(not critical)");
#ifdef LOGGING
	printf("about to start awaiting connections\n");
#endif
	
#ifdef TIMEOUT
	alarm(TIMEOUT);
#endif

	// loop accepting connections
	while (1)
	{
	
		sin_size=sizeof(gotConn);
		
		if ((sock_client = accept(sock_listen, (struct sockaddr *) &gotConn, &sin_size)) < 0)
		{
			SockError("Accept() failed");
		}

		// there's a new connection
		if(addr->s_addr && (gotConn.sin_addr.s_addr != addr->s_addr))
		{
#ifdef LOGGING
			fprintf(stderr,"connection from %s refused\n",inet_ntoa(gotConn.sin_addr));
#endif
			close(sock_client);
			continue;
	
		}else{
#ifdef TIMEOUT
			alarm(TIMEOUT);
#endif
		}

fork_retry:
			switch(fork())
			{
			case 0://child 
				close(sock_listen);
#ifdef LOGGING
				if (addr->s_addr)
				{
					fprintf(stderr,"[%d] from %s\n",getpid(),inet_ntoa(gotConn.sin_addr));
					fflush (stderr);
				}
#endif
				return proxy_handlereq(sock_client);
			case -1:
#ifdef LOGGING
				printf("Fork() failed, waiting...\n");
#endif
				if(waitpid(WAIT_ANY,NULL,0) < 0)
				{
					SockError("Error while waiting for child");
					return 2;
				}else{
					putchar('\n');
					goto fork_retry;
				}
				
			default://parent
				close(sock_client);
				while(waitpid(WAIT_ANY,NULL,WNOHANG) > 0);
			}
		}
	}			// 'while (1)'
	return 0;	// how on earth did we get here???
}			// 'proxy_server()'


//
// function which handles a single http request
//
int proxy_handlereq(SOCKET sock_client)
{
	close(0);
	if(dup(sock_client) < 0)//client -> child in
	{
		SockError("dup() failed");
		exit(3);
	}

	close(1);
	if(dup(sock_client) < 0)//child out -> client	
	{
		SockError("dup() failed");
		exit(3);
	}
	
	close(sock_client);
	execl(EXECUTE_NAME,EXECUTE_NAME,NULL);
	SockError("execl() failed");
	exit(3);

}		// 'proxy_handlereq()'

void sigcatch(int signo)
{
#ifdef LOGGING
	if(signo == SIGALRM) fprintf(stderr,"Timed out,");
	else fprintf(stderr,"Signal %d caught,",signo);

	fprintf(stderr," exiting.\n");
#endif
	exit(0);
}
